import { Option } from "naive-ui/es/transfer/src/interface";
import type { PlainFunction } from "./types";

export const keys = Reflect.ownKeys;
export const getPrototypeOf = Reflect.getPrototypeOf;

export const toString = Object.prototype.toString;

/* Boolean Detections */

export const toStringType = (obj: unknown) => toString.call(obj).slice(8, -1);

export const isUndefined = (obj: unknown): obj is undefined =>
  obj === undefined;
export const isNull = (obj: unknown): obj is null => obj === null;
export const isNone = (obj: unknown): obj is undefined | null => obj == null;
export const isNumber = (obj: unknown): obj is number =>
  typeof obj === "number";
export const isString = (obj: unknown): obj is string =>
  typeof obj === "string";
export const isBoolean = (obj: unknown): obj is boolean =>
  typeof obj === "boolean";
export const isObjectLike = (obj: unknown): obj is object =>
  typeof obj === "object" && obj !== null;

export const isFunction = (obj: unknown): obj is PlainFunction =>
  typeof obj === "function";

export const isSet = (obj: unknown) => toStringType(obj) === "Set";
export const isMap = (obj: unknown) => toStringType(obj) === "Map";
export const isArray = (obj: unknown) => Array.isArray(obj);
export const isInteger = Number.isInteger;
export const isNumberNaN = Number.isNaN;

export const isPlainObject = (obj: unknown) => {
  if (!isObjectLike(obj) || toStringType(obj) !== "Object") return false;

  if (getPrototypeOf(obj as object) === null) {
    return true;
  }

  let proto: object | null = obj as object;
  while (proto !== null && getPrototypeOf(proto) !== null) {
    proto = getPrototypeOf(proto);
  }

  return proto === getPrototypeOf(obj as object);
};

export const isEmpty = (obj: unknown) => {
  if (isString(obj) && (obj as string).length === 0) return true;
  if (isArray(obj) && (obj as unknown[]).length === 0) return true;
  if (isPlainObject(obj) && keys(obj as object).length === 0) return true;
  if (isMap(obj) && (obj as Map<unknown, unknown>).size === 0) return true;
  if (isSet(obj) && (obj as Set<unknown>).size === 0) return true;
  return false;
};

/**
 * 向已排序数组插入数据
 * @param src 待插入值
 * @param dest 已排序数组
 * @param key 排序键
 */
export function insertBy<T>(src: T, dest: T[], key: keyof T) {
  let inserted = false;
  for (let i = 0; i < dest.length && !inserted; i++) {
    if (src[key] < dest[i][key]) {
      dest.splice(i, 0, src);
      inserted = true;
    }
  }
  if (!inserted) dest.push(src);
}

/**
 * 获取全路径
 * @param path 当前路径
 * @param parentPath 父级路径
 */
export function getFullPath(path: string, parentPath: string) {
  let fullPath = "";
  if (path.startsWith("/")) fullPath = path;
  else if (parentPath === "/") fullPath = "/" + path;
  else if (parentPath.startsWith("/")) {
    fullPath = [parentPath, path].join("/");
  } else {
    fullPath = ["", parentPath, path].join("/");
  }
  return fullPath;
}

/**
 * 延迟函数
 * @param ms 毫秒数
 */
export function delay(ms: number) {
  return new Promise((res) => {
    setTimeout(res, ms);
  });
}

/**
 * 顺序(管道化)执行一组函数
 * @param funcs 执行序列
 */
export function compose(...funcs: ((x: any) => any)[]) {
  return (v: any) =>
    funcs.reduce((a, c) => {
      a = c(a);
      return a;
    }, v);
}

/**
 * 转换一个hash键值对成为一个 Option[]
 */
export function hash2Options(hash: any, parseToNumber = false): Option[] {
  return Object.entries(hash).map(([value, label]: [any, any]) => {
    if (parseToNumber) value = +value;
    return {
      label,
      value,
    };
  });
}

/**
 * 转换script文本成为URL，以便用于worker
 */
export function js2WorkerURL(scriptText: string) {
  const blob = new Blob([scriptText], {
    type: "application/javascript",
  });
  return URL.createObjectURL(blob);
}
